﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using Flatland.GeneticProgramming.CoreTypes;
using Flatland.GeneticProgramming.SyntaxTree;
using Flatland.Utilities;

/**
* 
* Copyright (c) 2012 Anders Høst Kjærgaard | ahkj@itu.dk
* 
* 
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
* associated documentation files (the "Software"), to deal in the Software without restriction, 
* including without limitation the rights to use, copy, modify, merge, publish, distribute, 
* sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial 
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 
* NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
namespace Flatland.GeneticProgramming.Mutations {
    /// <summary>
    /// Node replacement class
    /// </summary>
    class NodeReplacement : IMutation {
        public void Mutate(List<Report> reports, double mutationRate, int maxTreeDepth) {
            foreach (var report in reports) {
                PerformNodeReplacement(report, mutationRate, maxTreeDepth);
            }
        }

        /// <summary>
        /// Finds a random Node in the Tree and is restricted to replacing a function for a function, or a terminal for a terminal
        /// </summary>
        /// <param name="report"></param>
        /// <param name="mutationRate"> </param>
        public void PerformNodeReplacement(Report report, double mutationRate, int maxTreeDepth) {
            Debug.Assert(condition: report != null, message: "Argument was null", detailMessage: "Report in NodeReplacement was null");
            Debug.Assert(0.0d <= mutationRate, "probability was less than zero");
            if ((double)Utils.Random.Next(101) / 100 <= mutationRate) {
                uint depth = (uint)Utils.Random.Next(report.GenoType.Tree.MaxDetpth());
                _PerformNodeReplacementNodeReplacement(report.GenoType.Tree, report, depth);
                return;
            }
            if (UserPreferences.VERBOSE)
                Console.WriteLine("NodeReplacement was ignored with rate {0}", mutationRate);

        }

        private void _PerformNodeReplacementNodeReplacement(Tree tree, Report report, uint targetDepth) {
            Debug.Assert(condition: tree != null, message: "Argument was null", detailMessage: "Tree in NodeReplacement was null");

            if (0 < targetDepth && tree.GetType() != typeof(Leaf)) //Choose a random child if any
            {
                Node Node = (Node)tree;
                _PerformNodeReplacementNodeReplacement(Node.Children[Utils.Random.Next(Node.Children.Count)], report, targetDepth - 1);
            }

            //Function
            else if (tree.GetType() == typeof(Node)) {
                //Take a random offset in the Function set
                int max = report.PrimitiveSet.FunctionSet.Count;
                int index = Utils.Random.Next(max);
                //Itereate through all the available functions, and fint a suitable replacement, we iterate forever assuming that atleast the
                //targeted function itself must be in the set - so there must exist a match
                //TODO only iterate the number of Functions, circular but with an escape and some error handling
                for (; ; ) {
                    Function func = report.PrimitiveSet.FunctionSet[index];
                    if (func.Equals(((Node)tree).Function)) {
                        ((Node)tree).Function.MethodInfo = func.MethodInfo;
                        break;
                    }
                    index = ++index % max;
                }
            }

            //Terminal. Look for a random substitute or pick fist one found
            else if (tree.GetType() == typeof(Leaf)) {
                foreach (var term in report.PrimitiveSet.TerminalSet) {
                    Terminal rand = report.PrimitiveSet.TerminalSet[Utils.Random.Next(report.PrimitiveSet.TerminalSet.Count)];
                    if (rand.Equals(((Leaf)tree).Terminal)) {
                        ((Leaf)tree).Terminal = rand;
                        break;
                    }
                    if (term.Equals(((Leaf)tree).Terminal)) {
                        ((Leaf)tree).Terminal = term;
                        break;
                    }
                }
            }
            else
                Debug.Assert(false, message: "_NodeReplacement fails", detailMessage: "Reached end of method without taking any action");
        }
    }
}
